use salvo::prelude::*;
use salvo::oapi::extract::*;
use serde::{Deserialize, Serialize};
use crate::{service, json_ok, JsonResult};
use crate::models::rbac::menu::{SafeMenu, MenuTree};
use crate::service::rbac::menu::MenuService;
use salvo::http::StatusError;

use serde_json::Value;

#[derive(Debug, Serialize, Deserialize)]
pub struct CreateMenuData {
    pub name: String,
    pub path: Option<String>,
    pub component: Option<String>,
    pub icon: Option<String>,
    pub redirect: Option<String>,
    pub parent_id: Option<String>,
    pub order_num: i32,
    pub always_show: bool,
    pub hidden: bool,
    pub visible: bool,
    pub meta: Option<Value>,
}

#[derive(Debug, Deserialize)]
pub struct UpdateMenuData {
    pub name: Option<String>,
    pub path: Option<String>,
    pub component: Option<String>,
    pub redirect: Option<String>,
    pub parent_id: Option<String>,
    pub order_num: Option<i32>,
    pub always_show: Option<bool>,
    pub hidden: Option<bool>,
    pub meta: Option<Value>,
}

#[endpoint(tags("RBAC"))]
async fn create_menu(req: &mut Request) -> Result<Json<SafeMenu>, StatusError> {
    let db = service::conn();
    let menu_data = req.parse_json::<CreateMenuData>().await.map_err(|e| {
        StatusError::bad_request().brief(e.to_string())
    })?;
    
    use crate::utils::tenant::get_tenant_id;
    let tenant_id = get_tenant_id(req).map_err(|e| {
        StatusError::internal_server_error().brief(e.to_string())
    })?;
        
    match MenuService::create(
        db,
        &menu_data.name,
        menu_data.path.as_deref(),
        menu_data.component.as_deref(),
        menu_data.redirect.as_deref(),
        menu_data.hidden,
        menu_data.always_show,
        menu_data.meta.unwrap_or_default(),
        menu_data.parent_id.as_deref(),
        menu_data.order_num,
        tenant_id
    ).await {
        Ok(menu) => Ok(Json(SafeMenu {
            id: menu.id,
            name: menu.name,
            path: menu.path,
            component: menu.component,
            redirect: menu.redirect,
            parent_id: menu.parent_id,
            order_num: menu.order_num,
            meta: menu.meta,
            always_show: menu.always_show,
            hidden: menu.hidden,
            tenant_id: menu.tenant_id,
        })),
        Err(e) => Err(StatusError::internal_server_error().brief(e.to_string())),
    }
}

#[endpoint(tags("RBAC"), parameters(("menu_id", description = "ID of the menu to update")))]
async fn update_menu(req: &mut Request) -> Result<Json<SafeMenu>, StatusError> {
    let db = service::conn();
    let menu_id = req.param::<String>("menu_id")
        .ok_or_else(|| StatusError::bad_request().brief("Missing menu_id parameter"))?;
    let update_data = req.parse_json::<UpdateMenuData>().await.map_err(|e| {
        StatusError::bad_request().brief(e.to_string())
    })?;
    
    use crate::utils::tenant::get_tenant_id;
    let tenant_id = get_tenant_id(req).map_err(|e| {
        StatusError::internal_server_error().brief(e.to_string())
    })?;
        
    match MenuService::update(
        db,
        &menu_id,
        update_data.name.as_deref(),
        update_data.path.as_deref(),
        update_data.component.as_deref(),
        update_data.redirect.as_deref(),
        update_data.hidden,
        update_data.always_show,
        update_data.meta,
        update_data.parent_id.as_deref(),
        update_data.order_num,
        tenant_id
    ).await {
        Ok(menu) => Ok(Json(SafeMenu {
            id: menu.id,
            name: menu.name,
            path: menu.path,
            component: menu.component,
            redirect: menu.redirect,
            parent_id: menu.parent_id,
            order_num: menu.order_num,
            meta: menu.meta,
            always_show: menu.always_show,
            hidden: menu.hidden,
            tenant_id: menu.tenant_id,
        })),
        Err(e) => Err(StatusError::internal_server_error().brief(e.to_string())),
    }
}

#[endpoint(tags("RBAC"), parameters(("menu_id", description = "ID of the menu to delete")))]
async fn delete_menu(req: &mut Request) -> Result<StatusCode, StatusError> {
    let db = service::conn();
    let menu_id = req.param::<String>("menu_id")
        .ok_or_else(|| StatusError::bad_request().brief("Missing menu_id parameter"))?;
    
    use crate::utils::tenant::get_tenant_id;
    let tenant_id = get_tenant_id(req).map_err(|e| {
        StatusError::internal_server_error().brief(e.to_string())
    })?;
        
    match MenuService::delete(db, &menu_id, tenant_id).await {
        Ok(_) => Ok(StatusCode::NO_CONTENT),
        Err(e) => Err(StatusError::internal_server_error().brief(e.to_string())),
    }
}

#[endpoint(tags("RBAC"), parameters(("menu_id", description = "ID of the menu to retrieve")))]
async fn get_menu(menu_id: PathParam<String>, req: &mut Request) -> Result<Json<SafeMenu>, StatusError> {
    let menu_id = menu_id.into_inner();
    
    if menu_id.len() != 36 {
        return Err(StatusError::bad_request());
    }
    
    let db = service::conn();
    use crate::utils::tenant::get_tenant_id;
    let tenant_id = get_tenant_id(req).map_err(|e| {
        StatusError::internal_server_error().brief(e.to_string())
    })?;
        
    match MenuService::find_by_id(db, &menu_id, tenant_id).await {
        Ok(menu) => Ok(Json(SafeMenu {
            id: menu.id,
            name: menu.name,
            path: menu.path,
            component: menu.component,
            redirect: menu.redirect,
            parent_id: menu.parent_id,
            order_num: menu.order_num,
            meta: menu.meta,
            always_show: menu.always_show,
            hidden: menu.hidden,
            tenant_id: menu.tenant_id,
        })),
        Err(e) => Err(StatusError::internal_server_error().brief(e.to_string()))
    }
}

#[endpoint(tags("RBAC"))]
async fn list_menus(req: &mut Request) -> Result<Json<Vec<SafeMenu>>, StatusError> {
    let db = service::conn();
    
    use crate::utils::tenant::get_tenant_id;
    let tenant_id = get_tenant_id(req).map_err(|e| {
        StatusError::internal_server_error().brief(e.to_string())
    })?;
        
    match MenuService::find_all(db, tenant_id).await {
        Ok(menus) => Ok(Json(menus)),
        Err(e) => Err(StatusError::internal_server_error().brief(e.to_string())),
    }
}

#[endpoint(tags("RBAC"))]
async fn get_menu_tree(req: &mut Request) -> Result<Json<Vec<MenuTree>>, StatusError> {
    tracing::info!("get_menu_tree");
    let db = service::conn();
    
    use crate::utils::tenant::get_tenant_id;
    let tenant_id = get_tenant_id(req).map_err(|e| {
        StatusError::internal_server_error().brief(e.to_string())
    })?;
        
    match MenuService::find_tree(db, tenant_id).await {
        Ok(tree) => {
            tracing::info!("Menu tree fetched successfully");
            Ok(Json(tree))
        },
        Err(e) => {
            tracing::error!("Failed to fetch menu tree: {}", e);
            Err(StatusError::internal_server_error().brief(e.to_string()))
        }
    }
}

#[handler]
async fn menu_route_logger(req: &mut salvo::Request, depot: &mut salvo::Depot, res: &mut salvo::Response, ctrl: &mut salvo::FlowCtrl) {
    tracing::info!("Menu route request: {}", req.uri());
    ctrl.call_next(req, depot, res).await;
}

#[handler]
async fn menu_tree_logger(req: &mut salvo::Request, depot: &mut salvo::Depot, res: &mut salvo::Response, ctrl: &mut salvo::FlowCtrl) {
    tracing::info!("Menu tree route request: {}", req.uri());
    ctrl.call_next(req, depot, res).await;
}

pub fn routes() -> Router {
    tracing::info!("Registering menu routes");

    let menus_router = Router::with_path("menus")
        .hoop(menu_route_logger)
        .post(create_menu)
        .get(list_menus)
        .push(
            Router::with_path("{menu_id}")
                .hoop(salvo::logging::Logger::new())
                .get(get_menu)
                .put(update_menu)
                .delete(delete_menu)
        );
    
        Router::new()
    .push(menus_router)
    .push(Router::with_path("menusTree")
.get(get_menu_tree))
}
